/*jslint eqeq: true, plusplus: true, undef: true, sloppy: true, vars: true, forin: true */
(function ($) {

  var ms = $.mobiscroll,
    date = new Date(),
    defaults = {
    
      preset : 'datetime',
      mode: 'scroller',
      display: 'modal',
      theme: 'android',

      setText: '确定',
      cancelText: '取消',
      // Datetime component
      dateFormat: 'yy-mm-dd',
      dateOrder: 'yymmdd',
      dayNames: ['周日','周一','周二','周三','周四','周五','周六'],
      dayNamesShort: ['日','一','二','三','四','五','六'],
      dayText: '日',
      hourText: '时',
      minuteText: '分',
      monthNames: ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'],
      monthNamesShort: ['一','二','三','四','五','六','七','八','九','十','十一','十二'],
      monthText: '月',
      secText: '秒',
      timeFormat: 'HH:ii',
      timeWheels: 'HHii',
      yearText: '年',
      nowText: '当前',
      // Calendar component
      dateText: '日',
      timeText: '时间',
      calendarText: '日历',
      // Measurement components
      wholeText: 'Whole',
      fractionText: 'Fraction',
      unitText: 'Unit',
      // Time / Timespan component
      labels: ['Years', 'Months', 'Days', 'Hours', 'Minutes', 'Seconds', ''],
      labelsShort: ['Yrs', 'Mths', 'Days', 'Hrs', 'Mins', 'Secs', ''],
      // Timer component
      startText: 'Start',
      stopText: 'Stop',
      resetText: 'Reset',
      lapText: 'Lap',
      hideText: 'Hide',
      startYear: date.getFullYear() - 10,
      endYear: date.getFullYear() + 10,
      ampmText: '&nbsp;',
      nowText: 'Now',
      showNow: false,
      stepHour: 1,
      stepMinute: 1,
      stepSecond: 1,
      separator: ' '
    },
    preset = function (inst) {
      var that = $(this),
        html5def = {},
        format;
      // Force format for html5 date inputs (experimental)
      if (that.is('input')) {
        switch (that.attr('type')) {
        case 'date':
          format = 'yy-mm-dd';
          break;
        case 'datetime':
          format = 'yy-mm-ddTHH:ii:ssZ';
          break;
        case 'datetime-local':
          format = 'yy-mm-ddTHH:ii:ss';
          break;
        case 'month':
          format = 'yy-mm';
          html5def.dateOrder = 'mmyy';
          break;
        case 'time':
          format = 'HH:ii:ss';
          break;
        }
        // Check for min/max attributes
        var min = that.attr('min'),
          max = that.attr('max');
        if (min) {
          html5def.minDate = ms.parseDate(format, min);
        }
        if (max) {
          html5def.maxDate = ms.parseDate(format, max);
        }
      }

      // Set year-month-day order
      var i,
        k,
        keys,
        values,
        wg,
        start,
        end,
        orig = $.extend({}, inst.settings),
        s = $.extend(inst.settings, defaults, html5def, orig),
        offset = 0,
        wheels = [],
        ord = [],
        o = {},
        f = { y: 'getFullYear', m: 'getMonth', d: 'getDate', h: getHour, i: getMinute, s: getSecond, a: getAmPm },
        p = s.preset,
        dord = s.dateOrder,
        tord = s.timeWheels,
        regen = dord.match(/D/),
        ampm = tord.match(/a/i),
        hampm = tord.match(/h/),
        hformat = p == 'datetime' ? s.dateFormat + s.separator + s.timeFormat : p == 'time' ? s.timeFormat : s.dateFormat,
        defd = new Date(),
        stepH = s.stepHour,
        stepM = s.stepMinute,
        stepS = s.stepSecond,
        mind = s.minDate || new Date(s.startYear, 0, 1),
        maxd = s.maxDate || new Date(s.endYear, 11, 31, 23, 59, 59);

      format = format || hformat;

      if (p.match(/date/i)) {

        // Determine the order of year, month, day wheels
        $.each(['y', 'm', 'd'], function (j, v) {
          i = dord.search(new RegExp(v, 'i'));
          if (i > -1) {
            ord.push({ o: i, v: v });
          }
        });
        ord.sort(function (a, b) { return a.o > b.o ? 1 : -1; });
        $.each(ord, function (i, v) {
          o[v.v] = i;
        });

        wg = [];
        for (k = 0; k < 3; k++) {
          if (k == o.y) {
            offset++;
            values = [];
            keys = [];
            start = mind.getFullYear();
            end = maxd.getFullYear();
            for (i = start; i <= end; i++) {
              keys.push(i);
              values.push(dord.match(/yy/i) ? i : (i + '').substr(2, 2));
            }
            addWheel(wg, keys, values, s.yearText);
          } else if (k == o.m) {
            offset++;
            values = [];
            keys = [];
            for (i = 0; i < 12; i++) {
              var str = dord.replace(/[dy]/gi, '').replace(/mm/, i < 9 ? '0' + (i + 1) : i + 1).replace(/m/, (i + 1));
              keys.push(i);
              values.push(str.match(/MM/) ? str.replace(/MM/, '<span class="dw-mon">' + s.monthNames[i] + '</span>') : str.replace(/M/, '<span class="dw-mon">' + s.monthNamesShort[i] + '</span>'));
            }
            addWheel(wg, keys, values, s.monthText);
          } else if (k == o.d) {
            offset++;
            values = [];
            keys = [];
            for (i = 1; i < 32; i++) {
              keys.push(i);
              values.push(dord.match(/dd/i) && i < 10 ? '0' + i : i);
            }
            addWheel(wg, keys, values, s.dayText);
          }
        }
        wheels.push(wg);
      }

      if (p.match(/time/i)) {

        // Determine the order of hours, minutes, seconds wheels
        ord = [];
        $.each(['h', 'i', 's', 'a'], function (i, v) {
          i = tord.search(new RegExp(v, 'i'));
          if (i > -1) {
            ord.push({ o: i, v: v });
          }
        });
        ord.sort(function (a, b) {
          return a.o > b.o ? 1 : -1;
        });
        $.each(ord, function (i, v) {
          o[v.v] = offset + i;
        });

        wg = [];
        for (k = offset; k < offset + 4; k++) {
          if (k == o.h) {
            offset++;
            values = [];
            keys = [];
            for (i = 0; i < (hampm ? 12 : 24); i += stepH) {
              keys.push(i);
              values.push(hampm && i == 0 ? 12 : tord.match(/hh/i) && i < 10 ? '0' + i : i);
            }
            addWheel(wg, keys, values, s.hourText);
          } else if (k == o.i) {
            offset++;
            values = [];
            keys = [];
            for (i = 0; i < 60; i += stepM) {
              keys.push(i);
              values.push(tord.match(/ii/) && i < 10 ? '0' + i : i);
            }
            addWheel(wg, keys, values, s.minuteText);
          } else if (k == o.s) {
            offset++;
            values = [];
            keys = [];
            for (i = 0; i < 60; i += stepS) {
              keys.push(i);
              values.push(tord.match(/ss/) && i < 10 ? '0' + i : i);
            }
            addWheel(wg, keys, values, s.secText);
          } else if (k == o.a) {
            offset++;
            var upper = tord.match(/A/);
            addWheel(wg, [0, 1], upper ? ['AM', 'PM'] : ['am', 'pm'], s.ampmText);
          }
        }

        wheels.push(wg);
      }

      function get(d, i, def) {
        if (o[i] !== undefined) {
          return +d[o[i]];
        }
        if (def !== undefined) {
          return def;
        }
        return defd[f[i]] ? defd[f[i]]() : f[i](defd);
      }

      function addWheel(wg, k, v, lbl) {
        wg.push({
          values: v,
          keys: k,
          label: lbl
        });
      }

      function step(v, st) {
        return Math.floor(v / st) * st;
      }

      function getHour(d) {
        var hour = d.getHours();
        hour = hampm && hour >= 12 ? hour - 12 : hour;
        return step(hour, stepH);
      }

      function getMinute(d) {
        return step(d.getMinutes(), stepM);
      }

      function getSecond(d) {
        return step(d.getSeconds(), stepS);
      }

      function getAmPm(d) {
        return ampm && d.getHours() > 11 ? 1 : 0;
      }

      function getDate(d) {
        var hour = get(d, 'h', 0);
        return new Date(get(d, 'y'), get(d, 'm'), get(d, 'd', 1), get(d, 'a') ? hour + 12 : hour, get(d, 'i', 0), get(d, 's', 0));
      }

      // Extended methods
      // ---

      /**
       * Sets the selected date
       *
       * @param {Date} d Date to select.
       * @param {Boolean} [fill=false] Also set the value of the associated input element. Default is true.
       * @return {Object} jQuery object to maintain chainability
       */
      inst.setDate = function (d, fill, time, temp) {
        var i;

        // Set wheels
        for (i in o) {
          inst.temp[o[i]] = d[f[i]] ? d[f[i]]() : f[i](d);
        }
        inst.setValue(inst.temp, fill, time, temp);
      };

      /**
       * Returns the currently selected date.
       *
       * @param {Boolean} [temp=false] If true, return the currently shown date on the picker, otherwise the last selected one
       * @return {Date}
       */
      inst.getDate = function (temp) {
        return getDate(temp ? inst.temp : inst.values);
      };

      // ---

      return {
        button3Text: s.showNow ? s.nowText : undefined,
        button3: s.showNow ? function () { inst.setDate(new Date(), false, 0.3, true); } : undefined,
        wheels: wheels,
        headerText: function (v) {
          return ms.formatDate(hformat, getDate(inst.temp), s);
        },
        /**
        * Builds a date object from the wheel selections and formats it to the given date/time format
        * @param {Array} d - An array containing the selected wheel values
        * @return {String} - The formatted date string
        */
        formatResult: function (d) {
          return ms.formatDate(format, getDate(d), s);
        },
        /**
        * Builds a date object from the input value and returns an array to set wheel values
        * @return {Array} - An array containing the wheel values to set
        */
        parseValue: function (val) {
          var d = new Date(),
            i,
            result = [];
          try {
            d = ms.parseDate(format, val, s);
          } catch (e) {
          }
          // Set wheels
          for (i in o) {
            result[o[i]] = d[f[i]] ? d[f[i]]() : f[i](d);
          }
          return result;
        },
        /**
        * Validates the selected date to be in the minDate / maxDate range and sets unselectable values to disabled
        * @param {Object} dw - jQuery object containing the generated html
        * @param {Integer} [i] - Index of the changed wheel, not set for initial validation
        */
        validate: function (dw, i) {
          var temp = inst.temp, //.slice(0),
            mins = { y: mind.getFullYear(), m: 0, d: 1, h: 0, i: 0, s: 0, a: 0 },
            maxs = { y: maxd.getFullYear(), m: 11, d: 31, h: step(hampm ? 11 : 23, stepH), i: step(59, stepM), s: step(59, stepS), a: 1 },
            minprop = true,
            maxprop = true;
          $.each(['y', 'm', 'd', 'a', 'h', 'i', 's'], function (x, i) {
            if (o[i] !== undefined) {
              var min = mins[i],
                max = maxs[i],
                maxdays = 31,
                val = get(temp, i),
                t = $('.dw-ul', dw).eq(o[i]),
                y,
                m;
              if (i == 'd') {
                y = get(temp, 'y');
                m = get(temp, 'm');
                maxdays = 32 - new Date(y, m, 32).getDate();
                max = maxdays;
                if (regen) {
                  $('.dw-li', t).each(function () {
                    var that = $(this),
                      d = that.data('val'),
                      w = new Date(y, m, d).getDay(),
                      str = dord.replace(/[my]/gi, '').replace(/dd/, d < 10 ? '0' + d : d).replace(/d/, d);
                    $('.dw-i', that).html(str.match(/DD/) ? str.replace(/DD/, '<span class="dw-day">' + s.dayNames[w] + '</span>') : str.replace(/D/, '<span class="dw-day">' + s.dayNamesShort[w] + '</span>'));
                  });
                }
              }
              if (minprop && mind) {
                min = mind[f[i]] ? mind[f[i]]() : f[i](mind);
              }
              if (maxprop && maxd) {
                max = maxd[f[i]] ? maxd[f[i]]() : f[i](maxd);
              }
              if (i != 'y') {
                var i1 = $('.dw-li', t).index($('.dw-li[data-val="' + min + '"]', t)),
                  i2 = $('.dw-li', t).index($('.dw-li[data-val="' + max + '"]', t));
                $('.dw-li', t).removeClass('dw-v').slice(i1, i2 + 1).addClass('dw-v');
                if (i == 'd') { // Hide days not in month
                  $('.dw-li', t).removeClass('dw-h').slice(maxdays).addClass('dw-h');
                }
              }
              if (val < min) {
                val = min;
              }
              if (val > max) {
                val = max;
              }
              if (minprop) {
                minprop = val == min;
              }
              if (maxprop) {
                maxprop = val == max;
              }
              // Disable some days
              if (s.invalid && i == 'd') {
                var idx = [];
                // Disable exact dates
                if (s.invalid.dates) {
                  $.each(s.invalid.dates, function (i, v) {
                    if (v.getFullYear() == y && v.getMonth() == m) {
                      idx.push(v.getDate() - 1);
                    }
                  });
                }
                // Disable days of week
                if (s.invalid.daysOfWeek) {
                  var first = new Date(y, m, 1).getDay(),
                    j;
                  $.each(s.invalid.daysOfWeek, function (i, v) {
                    for (j = v - first; j < maxdays; j += 7) {
                      if (j >= 0) {
                        idx.push(j);
                      }
                    }
                  });
                }
                // Disable days of month
                if (s.invalid.daysOfMonth) {
                  $.each(s.invalid.daysOfMonth, function (i, v) {
                    v = (v + '').split('/');
                    if (v[1]) {
                      if (v[0] - 1 == m) {
                        idx.push(v[1] - 1);
                      }
                    } else {
                      idx.push(v[0] - 1);
                    }
                  });
                }
                $.each(idx, function (i, v) {
                  $('.dw-li', t).eq(v).removeClass('dw-v');
                });
              }

              // Set modified value
              temp[o[i]] = val;
            }
          });
        }
      };
    };

  $.each(['date', 'time', 'datetime'], function (i, v) {
    ms.presets[v] = preset;
    ms.presetShort(v);
  });

  /**
  * Format a date into a string value with a specified format.
  * @param {String} format - Output format.
  * @param {Date} date - Date to format.
  * @param {Object} settings - Settings.
  * @return {String} - Returns the formatted date string.
  */
  ms.formatDate = function (format, date, settings) {
    if (!date) {
      return null;
    }
    var s = $.extend({}, defaults, settings),
      look = function (m) { // Check whether a format character is doubled
        var n = 0;
        while (i + 1 < format.length && format.charAt(i + 1) == m) {
          n++;
          i++;
        }
        return n;
      },
      f1 = function (m, val, len) { // Format a number, with leading zero if necessary
        var n = '' + val;
        if (look(m)) {
          while (n.length < len) {
            n = '0' + n;
          }
        }
        return n;
      },
      f2 = function (m, val, s, l) { // Format a name, short or long as requested
        return (look(m) ? l[val] : s[val]);
      },
      i,
      output = '',
      literal = false;

    for (i = 0; i < format.length; i++) {
      if (literal) {
        if (format.charAt(i) == "'" && !look("'")) {
          literal = false;
        } else {
          output += format.charAt(i);
        }
      } else {
        switch (format.charAt(i)) {
        case 'd':
          output += f1('d', date.getDate(), 2);
          break;
        case 'D':
          output += f2('D', date.getDay(), s.dayNamesShort, s.dayNames);
          break;
        case 'o':
          output += f1('o', (date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000, 3);
          break;
        case 'm':
          output += f1('m', date.getMonth() + 1, 2);
          break;
        case 'M':
          output += f2('M', date.getMonth(), s.monthNamesShort, s.monthNames);
          break;
        case 'y':
          output += (look('y') ? date.getFullYear() : (date.getYear() % 100 < 10 ? '0' : '') + date.getYear() % 100);
          break;
        case 'h':
          var h = date.getHours();
          output += f1('h', (h > 12 ? (h - 12) : (h == 0 ? 12 : h)), 2);
          break;
        case 'H':
          output += f1('H', date.getHours(), 2);
          break;
        case 'i':
          output += f1('i', date.getMinutes(), 2);
          break;
        case 's':
          output += f1('s', date.getSeconds(), 2);
          break;
        case 'a':
          output += date.getHours() > 11 ? 'pm' : 'am';
          break;
        case 'A':
          output += date.getHours() > 11 ? 'PM' : 'AM';
          break;
        case "'":
          if (look("'")) {
            output += "'";
          } else {
            literal = true;
          }
          break;
        default:
          output += format.charAt(i);
        }
      }
    }
    return output;
  };

  /**
  * Extract a date from a string value with a specified format.
  * @param {String} format - Input format.
  * @param {String} value - String to parse.
  * @param {Object} settings - Settings.
  * @return {Date} - Returns the extracted date.
  */
  ms.parseDate = function (format, value, settings) {
    var def = new Date();

    if (!format || !value) {
      return def;
    }

    value = (typeof value == 'object' ? value.toString() : value + '');

    var s = $.extend({}, defaults, settings),
      shortYearCutoff = s.shortYearCutoff,
      year = def.getFullYear(),
      month = def.getMonth() + 1,
      day = def.getDate(),
      doy = -1,
      hours = def.getHours(),
      minutes = def.getMinutes(),
      seconds = 0, //def.getSeconds(),
      ampm = -1,
      literal = false, // Check whether a format character is doubled
      lookAhead = function (match) {
        var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
        if (matches) {
          iFormat++;
        }
        return matches;
      },
      getNumber = function (match) { // Extract a number from the string value
        lookAhead(match);
        var size = (match == '@' ? 14 : (match == '!' ? 20 : (match == 'y' ? 4 : (match == 'o' ? 3 : 2)))),
          digits = new RegExp('^\\d{1,' + size + '}'),
          num = value.substr(iValue).match(digits);

        if (!num) {
          return 0;
        }
        //throw 'Missing number at position ' + iValue;
        iValue += num[0].length;
        return parseInt(num[0], 10);
      },
      getName = function (match, s, l) { // Extract a name from the string value and convert to an index
        var names = (lookAhead(match) ? l : s),
          i;

        for (i = 0; i < names.length; i++) {
          if (value.substr(iValue, names[i].length).toLowerCase() == names[i].toLowerCase()) {
            iValue += names[i].length;
            return i + 1;
          }
        }
        return 0;
        //throw 'Unknown name at position ' + iValue;
      },
      checkLiteral = function () {
        //if (value.charAt(iValue) != format.charAt(iFormat))
        //throw 'Unexpected literal at position ' + iValue;
        iValue++;
      },
      iValue = 0,
      iFormat;

    for (iFormat = 0; iFormat < format.length; iFormat++) {
      if (literal) {
        if (format.charAt(iFormat) == "'" && !lookAhead("'")) {
          literal = false;
        } else {
          checkLiteral();
        }
      } else {
        switch (format.charAt(iFormat)) {
        case 'd':
          day = getNumber('d');
          break;
        case 'D':
          getName('D', s.dayNamesShort, s.dayNames);
          break;
        case 'o':
          doy = getNumber('o');
          break;
        case 'm':
          month = getNumber('m');
          break;
        case 'M':
          month = getName('M', s.monthNamesShort, s.monthNames);
          break;
        case 'y':
          year = getNumber('y');
          break;
        case 'H':
          hours = getNumber('H');
          break;
        case 'h':
          hours = getNumber('h');
          break;
        case 'i':
          minutes = getNumber('i');
          break;
        case 's':
          seconds = getNumber('s');
          break;
        case 'a':
          ampm = getName('a', ['am', 'pm'], ['am', 'pm']) - 1;
          break;
        case 'A':
          ampm = getName('A', ['am', 'pm'], ['am', 'pm']) - 1;
          break;
        case "'":
          if (lookAhead("'")) {
            checkLiteral();
          } else {
            literal = true;
          }
          break;
        default:
          checkLiteral();
        }
      }
    }
    if (year < 100) {
      year += new Date().getFullYear() - new Date().getFullYear() % 100 +
        (year <= (typeof shortYearCutoff != 'string' ? shortYearCutoff : new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)) ? 0 : -100);
    }
    if (doy > -1) {
      month = 1;
      day = doy;
      do {
        var dim = 32 - new Date(year, month - 1, 32).getDate();
        if (day <= dim) {
          break;
        }
        month++;
        day -= dim;
      } while (true);
    }
    hours = (ampm == -1) ? hours : ((ampm && hours < 12) ? (hours + 12) : (!ampm && hours == 12 ? 0 : hours));
    var date = new Date(year, month - 1, day, hours, minutes, seconds);
    if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day) {
      throw 'Invalid date';
    }
    return date;
  };

})(jQuery);
